#include "cc_mempool.h"
#include "cc_log.h"

/**
 * @brief dynamic create a mempool
 *
 * @param block_num the block num of the mempool
 * @param blocksize the block size
 * @return mempool pool pointer
 *
 */
cc_mempool_t cc_mempool_create(size_t block_num, size_t block_size)
{
  cc_mempool_t pool;
  pool = (cc_mempool_t)malloc(sizeof(cc_mempool));
  if (!pool)
    return CC_NULL;

  size_t data_size = block_num * block_size;

  void* data = (void*)malloc(data_size);
  pool->mem_addr = data;
  (void)cc_mempool_init(pool, data, data_size, block_size);
  return pool;
}

/**
 * @brief static create a mempool
 *
 * @param pool the mem pool pointer
 * @param data the mempool data
 * @param sz the data size
 * @param blocksize the every block size
 *
 */
int cc_mempool_init(cc_mempool_t pool, void* data, size_t sz,
                    size_t block_size)
{
  int cnt;
  pool->free_list = CC_NULL;
  pool->alloc_list = CC_NULL;

  cc_mutex_init(&pool->mutex);
  cc_cond_init(&pool->cond);
  cnt = sz / (block_size);

  for (int i = 0; i < cnt; i++)
  {
    cc_mempool_block* block = CC_NULL;

    block = (cc_mempool_block*)data;
    data = (void*)(((uint8_t*)data) + block_size);
    if (block == CC_NULL)
    {
      cc_log_e("Memory allocation failed\n");
      return CC_ENOMEM;
    }
    block->next = pool->free_list;
    pool->free_list = block;
  }
  return CC_EOK;
}

/**
 * @brief destroy a mempool
 *
 * @param pool the mem pool pointer
 * @return error_code
 */
int cc_mempool_destroy(cc_mempool_t pool)
{
  if (pool == CC_NULL)
    return CC_ENOMEM;
  cc_mutex_lock(&pool->mutex);
  if (pool->alloc_list != NULL)
  {
    return CC_ENOMEM;
  }
  cc_free(pool->mem_addr);
  pool->mem_addr = CC_NULL;
  cc_mempool_deinit(pool);
  cc_free(pool);
  return CC_EOK;
}

int cc_mempool_deinit(cc_mempool_t pool)
{
  if (pool == CC_NULL)
    return -CC_EINVAL;
  cc_mutex_unlock(&pool->mutex);

  cc_mutex_deinit(&pool->mutex);
  cc_cond_deinit(&pool->cond);
  return cc_true;
}
/**
 * @brief get a block mem from the pool
 *
 * @param pool the mem pool pointer
 * @param timeout_ms delay_time
 * @return data_address
 */
void* cc_mempool_alloc_time(cc_mempool_t pool, uint32_t timeout_ms)
{
  if (pool == CC_NULL)
    return CC_NULL;
  cc_mutex_lock(&pool->mutex);

  while (pool->free_list == CC_NULL)
  {
    int result = cc_cond_wait_ms(&pool->cond, timeout_ms);
    if (result == cc_false)
    {
      cc_mutex_unlock(&pool->mutex);
      return CC_NULL;
    }
  }
  cc_mempool_block* block = pool->free_list;
  pool->free_list = block->next;
  block->next = pool->alloc_list;
  pool->alloc_list = block;

  cc_mutex_unlock(&pool->mutex);
  return ((void*)block) + sizeof(cc_mempool_block);
}

void* cc_mempool_try_alloc(cc_mempool_t pool)
{
  return cc_mempool_alloc_time(pool, 0);
}
void* cc_mempool_alloc(cc_mempool_t pool)
{
  return cc_mempool_alloc_time(pool, CC_TIME_WAIT_FOREVER);
}
/**
 * @brief free a block mem to the pool
 *
 * @param pool the mem pool pointer
 * @param m data_address
 * @return error code
 */
int cc_mempool_free(cc_mempool_t pool, void* m)
{
  if (m == CC_NULL || pool == CC_NULL)
  {
    return CC_ENOMEM;
  }

  cc_mempool_block* block =
      (cc_mempool_block*)(((uint8_t*)m) - sizeof(cc_mempool_block));

  cc_mutex_lock(&pool->mutex);
  if (pool->alloc_list == block)
  {
    pool->alloc_list = block->next;
  }
  else
  {
    cc_mempool_block* current = pool->alloc_list;
    while (current != CC_NULL && current->next != block)
    {
      current = current->next;
    }
    if (current != CC_NULL)
    {
      current->next = block->next;
    }
  }

  block->next = pool->free_list;
  pool->free_list = block;
  // pool->alloc_list = CC_NULL;
  cc_cond_broadcast(&pool->cond);

  cc_mutex_unlock(&pool->mutex);
  return CC_EOK;
}
